package com.hjq.mallproduct.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.hjq.mallproduct.entity.vo.Catlog2VO;
import com.hjq.mallproduct.entity.vo.Catlog3VO;
import com.hjq.mallproduct.service.CategoryBrandRelationService;
import com.mysql.cj.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.C;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hjq.common.utils.PageUtils;
import com.hjq.common.utils.Query;

import com.hjq.mallproduct.dao.CategoryDao;
import com.hjq.mallproduct.entity.CategoryEntity;
import com.hjq.mallproduct.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service("categoryService")
@Transactional
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Lazy
    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listAllWithTree() {
        /**
         * 1.设置null也进缓存，解决缓存击穿
         * 2，设置随机过期时间解决缓存穿透
         * 3‘设置锁解决缓存穿透
         */
        QueryWrapper<CategoryEntity> categoryEntityQueryWrapper = new QueryWrapper<>();
        categoryEntityQueryWrapper.select("cat_id","name","parent_cid","cat_level","show_status","sort","icon","product_unit","product_count");
        //查询所有商品
        List<CategoryEntity> categoryEntities = baseMapper.selectList( categoryEntityQueryWrapper);
        //将商品分类，一级分类先
        List<CategoryEntity> firstLevelProducts;
        //设置完整树
        firstLevelProducts = categoryEntities.stream()
                .filter(categoryEntity ->  categoryEntity.getParentCid() == 0)
                .map(categoryEntity -> {
                    categoryEntity.setSonCategoryEntityList(getChildren(categoryEntity,categoryEntities));
                    return categoryEntity;
                })
                .sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort())))
                .collect(Collectors.toList());
        //设置完整树



        return firstLevelProducts;
    }

    @Override
    public void removeBatchByIds(List<Long> asList) {
       //TODO  检查是否被其它地方引用了
        baseMapper.deleteBatchIds(asList);
        //常使用逻辑删除

    }

    @Override
    public CategoryEntity getInfoById(Long catId) {
        QueryWrapper<CategoryEntity> getInfoWrapper = new QueryWrapper<>();
        getInfoWrapper.select("cat_id", "name", "parent_cid", "cat_level", "show_status", "sort", "icon", "product_unit", "product_count")
                .eq("cat_id", catId);
        return baseMapper.selectOne(getInfoWrapper);
    }

    @Override
    public List<Long> getCategoryPath(Long catelogId) {
        ArrayList<Long> arrayList = new ArrayList<>();
        Stack<Long> longStack = new Stack<>();
        Long catId=catelogId;
        Long parentId;
        do {
            longStack.push(catId);
            parentId=getParentCid(catId);
            catId=parentId;
        }while (parentId!=0L);
        while (!longStack.empty()){
            Long pop = longStack.pop();
            arrayList.add(pop);
        }

        return arrayList;
    }

    @Override
    @Caching(evict = {
            @CacheEvict(value ={"category"},key = "'getFirstLevelList'" ),
            @CacheEvict(value ={"category"},key = "'getCategoryListInPage'" )
    })

    public void updateCasecade(CategoryEntity category) {
        Long catId = category.getCatId();
        String categoryName = category.getName();
        this.updateById(category);
        if (!StringUtils.isEmptyOrWhitespaceOnly(categoryName)){
            categoryBrandRelationService.updareCategory(catId,categoryName);

        }
    }

    @Cacheable(value = {"category"},key = "'getFirstLevelList'",sync = true)
    @Override
    //代表昂强结果需要缓存，如果缓存中有，不用调用
    //每一个需要缓存的数据需要保证名字区分不同分区，按照业务功能类型分区
    /**
     * 默认行为：1.缓存中有，方法不调用
     * 2.key默认自动生成
     * 3.默认时减：永不过期
     */
    public List<CategoryEntity> getFirstLevelList() {
        System.out.println("一级菜单");

        return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("cat_level", 1));

    }

    /**
     *
     * @return redisson解决分布式锁
     * 1.需要解决数据不同步问题
     * 1）双写模式
     * 2）失效模式：改玩直接删掉
     */

    public Map<String, List<Catlog2VO>> getCategoryListInPageWithRedisson() {

        //
        RLock listLock = redissonClient.getLock("getCategoryListLock");
        listLock.lock(30,TimeUnit.SECONDS);
        Map<String, List<Catlog2VO>> categoryListInPageFromDb;
            try {
                categoryListInPageFromDb=getCategoryListInPageWithRedis();
                }
            finally {
                listLock.unlock();
            }
            return categoryListInPageFromDb;


    }

    /**
     * 原生redis实现分布式锁
     * @return
     */

    public Map<String, List<Catlog2VO>> getCategoryListInPageWithRedisLock() {
        String uuid = UUID.randomUUID().toString();
        //使用原子命令设置分布式锁，防止宕机出问题
        Boolean isLock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
        if(isLock){
            //抢到锁了，给锁设置过期时间即使宕机也能自动删除；但是必须和设置锁同时，否则中断的话会出问题
            //stringRedisTemplate.expire("lock",10, TimeUnit.SECONDS);
            //抢到锁了，执行缓存逻辑
            Map<String, List<Catlog2VO>> categoryListInPageFromDb;
            try {
                categoryListInPageFromDb=getCategoryListInPageWithRedis();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //原子删除锁，保证删除
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script,Long.class),Arrays.asList("lock"),uuid);

            }
            return categoryListInPageFromDb;
            //释放锁
           // stringRedisTemplate.delete("lock");
//            String getlock = stringRedisTemplate.opsForValue().get("lock");
//            if(uuid.equals(getlock)){
//                //判断是否是自己的所，是自己的才能删除//但是会遇到和之前一样不是原子性操作的问题
//                stringRedisTemplate.delete("lock");
//            }

        }else {
            //用自旋的方式重试
            try{
                Thread.sleep(20);
            }
            catch (Exception e){

            }
            return getCategoryListInPage();
        }
    }

    public Map<String, List<Catlog2VO>> getCategoryListInPageWithRedis() {
    //给redis放入json，逆转为能用的类型[序列化和反序列化]

            //只有一个单例，因此可以使用this锁住
            Map<String, List<Catlog2VO>> categoryListInPageFromDb;
            String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
            if(StringUtils.isNullOrEmpty(catalogJson)){
                categoryListInPageFromDb = getCategoryListInPageFromDb();
                String toJSONString = JSON.toJSONString(categoryListInPageFromDb);
                stringRedisTemplate.opsForValue().set("catalogJson",toJSONString);


            }else {
                //转为指定的类型
                categoryListInPageFromDb=JSON.parseObject(catalogJson,new TypeReference<Map<String, List<Catlog2VO>>>(){});



            }
            return categoryListInPageFromDb;



    }

    @Override
    @Cacheable(value ={"category"},key = "'getCategoryListInPage'" )
    public Map<String, List<Catlog2VO>> getCategoryListInPage() {
        List<CategoryEntity> firstLevelList = getFirstLevelList();
        Map<String, List<Catlog2VO>> listMap = firstLevelList.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            List<CategoryEntity> child2 = list(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
            List<Catlog2VO> catlog2VOS = child2.stream().map(item -> {
                Catlog2VO catlog2VO = new Catlog2VO();
                catlog2VO.setCatalog1Id(item.getParentCid());
                catlog2VO.setId(item.getCatId());
                catlog2VO.setName(item.getName());
                List<CategoryEntity> childs3 = list(new QueryWrapper<CategoryEntity>().eq("parent_cid", item.getCatId()));
                List<Catlog3VO> catlog3VOS = childs3.stream().map(child3 -> {
                    Catlog3VO catlog3VO = new Catlog3VO();
                    catlog3VO.setCatalog2Id(child3.getParentCid());
                    catlog3VO.setId(child3.getCatId());
                    catlog3VO.setName(child3.getName());
                    return catlog3VO;
                }).collect(Collectors.toList());
                catlog2VO.setCatalog3List(catlog3VOS);
                return catlog2VO;

            }).collect(Collectors.toList());
            return catlog2VOS;
        }));
        return listMap;
    }

    public Map<String, List<Catlog2VO>> getCategoryListInPageFromDb() {
        List<CategoryEntity> firstLevelList = getFirstLevelList();
        Map<String, List<Catlog2VO>> listMap = firstLevelList.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
            List<CategoryEntity> child2 = list(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));
            List<Catlog2VO> catlog2VOS = child2.stream().map(item -> {
                Catlog2VO catlog2VO = new Catlog2VO();
                catlog2VO.setCatalog1Id(item.getParentCid());
                catlog2VO.setId(item.getCatId());
                catlog2VO.setName(item.getName());
                List<CategoryEntity> childs3 = list(new QueryWrapper<CategoryEntity>().eq("parent_cid", item.getCatId()));
                List<Catlog3VO> catlog3VOS = childs3.stream().map(child3 -> {
                    Catlog3VO catlog3VO = new Catlog3VO();
                    catlog3VO.setCatalog2Id(child3.getParentCid());
                    catlog3VO.setId(child3.getCatId());
                    catlog3VO.setName(child3.getName());
                    return catlog3VO;
                }).collect(Collectors.toList());
                catlog2VO.setCatalog3List(catlog3VOS);
                return catlog2VO;

            }).collect(Collectors.toList());
            return catlog2VOS;
        }));
        return listMap;

    }

    private List<CategoryEntity> getChildren(CategoryEntity categoryEntity, List<CategoryEntity> categoryEntities) {
        List<CategoryEntity> entities = categoryEntities.stream()
                .filter(categoryEntity1 ->
                        categoryEntity1.getParentCid() == categoryEntity.getCatId())
                .map(menu ->
                        {
                            menu.setSonCategoryEntityList(getChildren(menu, categoryEntities));
                            return menu;
                        }
                )
                .sorted(Comparator.comparingInt(menu -> (menu.getSort() == null ? 0 : menu.getSort())))
                .collect(Collectors.toList());
        return   entities;
    }
    private Long getParentCid(Long catId) {
        QueryWrapper<CategoryEntity> wrapper = new QueryWrapper<>();
        wrapper.select("cat_id", "name", "parent_cid", "cat_level", "show_status", "sort", "icon", "product_unit", "product_count").eq("cat_id", catId);
        CategoryEntity categoryEntity = baseMapper.selectOne(wrapper);
       return   categoryEntity.getParentCid();
    }

}